java快速学习速查(7)[进阶篇]
java快速学习速查(7)[进阶篇]
JBDC
JDBC:java语言连接数据库,通过java语言操作数据库中的数据。JDBC(Java Database Connectivity)是sun公司指定的一套标准规范,由很多的类和接口组成,在java.sql.*包下。
JDBC本质上是sun公司提供的一套接口,接口的实现类由数据库厂商提供。
- sum公司定义的一套操作所有关系型数据库的规范,即接口。
 - 各个数据库厂商去实现这套接口,提供数据库驱动jar包。
 - 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
 
这玩意的存在,为世界上所有的关系型数据库提供了统一的访问方式。是编程中访问数据库的标准规范。
各数据库厂商使用相同的接口,java代码不需要针对不同的数据库分别开发。
可以随时替换叠层数据库,访问数据库的java代码基本不变,以后编写操作数据库的代码只需要面向JDBC(接口),操作哪个关系型数据库就需要导入该数据库的驱动jar包,需要操作mysql数据库,就需要在项目中导入mysql数据库的驱动包。
java访问数据库(流程和对应的方法)
在java中访问数据库有几个步骤:
1.加载并注册数据库驱动
2.建立数据连接(URL书写,用户名,密码)
3.创建Statement
4.执行对数据库的增删改查操作
5.结果集处理
对应以上步骤的方法名是:
- 加载并注册数据库驱动:Class.forName(“com.mysql.jdbc.Driver”);
 - 建立数据连接:Connection conn = DriverManager.getConnection(“jdbc:mysql://localhost:3306/db_name”, “username”, “password”);
 - 创建Statement:Statement stmt = conn.createStatement();
 - 执行对数据库的增删改查操作:int rowsAffected = stmt.executeUpdate(“INSERT INTO users (name, age) VALUES (‘John Doe’, 30)”);
 - 结果集处理:ResultSet rs = stmt.executeQuery(“SELECT * FROM users”);
 - 关闭连接:rs.close(); stmt.close(); conn.close();
 
我们在进行数据库和java连接前,请一定注意将对应版本的驱动包导入项目中。
(mysql-connector-java-5.1.37-bin.jar)添加到工程中的classpath里面(右键add as library),我们可以直接拷贝jar包。
连接方式上,我们可以直接在对应的行为类中进行连接,也可以在工具类中进行连接。
直接连接1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package com.iweb.test
import java.sql.Connection;
import java.sql.DriverManager;
public class Test1 {
    public static void main(String[] args) throws  Exception {
        // 加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 驱动管理类获得连接对象
        // url:连接的数据库的地址
        // user: 连接数据库的账号
        // pass: 连接数据库的密码
        String url="jdbc:mysql://localhost:3306/mydb";
        String user="root";
        String pass="root";
        // DriverManager驱动管理类获得了一个数据库连接对象
        // Connection 接口负责java程序与数据库之间的连接
        Connection cn=DriverManager.getConnection(url,user,pass);
        System.out.println(cn);
   }
}
用其他的配置文件保存数据库配置信息(更推荐)
配置文件生命在工程的src目录下【jdbc.properties】
说明:使用配置文件的方式保存数据库配置信息,在代码中加载配置文件。
使用配置文件的好处:
1.实现了代码和数据分离,如果需要修改数据库配置信息,直接在配置文中修改,不需要深入代码。
2.如果修改了配置信息,省去重新编译的过程。
配置文件生命在工程的src目录下【jdbc.properties】1
2
3url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8
user=root
pass=root
1  | package com.iweb.test;  | 
statement
操作和访问数据库
数据库连接用于向数据库服务器发送命令和SQL语句,并接受数据库服务器返回的结果,其实就一个数据库连接就是一个socket连接。
在java.sql包中有3各接口,分别定了对数据库的调用的不同方式
- Statement:用于执行静态SQL语句并返回它所生成的结果的对象。
 - PrepatedStatement:SQL语句预编译对象,可以使用对象多次高效的执行该SQL语句。
 
statement
createStatement()用于创建statement对象,我们通过statement对象来完成对数据库的增删改查操
作。其实就是通过statement对象来将SQL语句发送给数据库,然后执行SQL语句。
该方法的具体的使用方法为:1
Statement st = cn.createStatement();
此处注意一下啊:
statement只能执行静态SQL语句,会有安全隐患,这个会在PrepatedStatement:SQL接口的时候讲解。(statement不推荐使用)
执行增删改查
执行对数据库的操作主要是执行statement中的方法。
执行给定的SQL语句,语句为insert、update、和delete语句,返回值是语句响应的行数。1
int executeUpdate(String sql)
实际实例:(DML操作)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48package com.iweb.test;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Properties;
public class Test2 {2).查询数据
执行给定的查询的SQL语句,该语句返回ResultSet对象。
ResultSet(结果集对象)封装了SQL查询语句的结果
1.通过列名
    public static void main(String[] args)  throws  Exception{
        // 加载jdbc.properties文件
        InputStream resourceAsStream 
=Test2.class.getClassLoader().getResourceAsStream("jdbc.properties");
        // Properties 继承于 HashTable,主要用于读取配置文件
        Properties pros=new Properties();
        // 加载文本信息到属性集
        pros.load(resourceAsStream);
        // 读取配置信息,依据KEY获取Value
        String url = pros.getProperty("url");
        String user = pros.getProperty("user");
        String pass = pros.getProperty("pass");
        // 1.加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 2.驱动管理类获得一个连接对象
        Connection cn = DriverManager.getConnection(url, user, pass);
        // 3.通过Connection对象获得数据库操作工具(statement)
        Statement st = cn.createStatement();
        // 4.数据库操作工具(statement)来执行SQL语句
        // st.executeQuery()用于执行查询操作 DQL
        // st.executeUpdate() 用于执行增删改操作 DML
        // 新增
        // String sql="insert into emp 
values(null,'1012','wangjie','男',55,'4313','2021-2-2',10,3000)";
        // 修改
        // String sql="update emp set name='王杰',age=58 where id=30";
        // 删除
        String sql="delete from emp where id=30";
        int result = st.executeUpdate(sql);
        if(result>0){
            System.out.println("删除成功");
       }else{
            System.out.println("删除失败");
       }
        st.close();
        cn.close();
   }
}
查询数据操作
执行给定的查询的SQL语句,该语句返回ResultSet对象。1
ResultSet executeQuery(sql)
ResultSet(结果集对象)封装了SQL查询语句的结果
通过列名查找1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45package com.iweb.test;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class Test3 {
    public static void main(String[] args)  throws  Exception {
        Class.forName("com.mysql.jdbc.Driver");
        InputStream is = 
Test3.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties prop = new Properties();
        prop.load(is);
        String url = prop.getProperty("url");
        String user = prop.getProperty("user");
        String pass = prop.getProperty("pass");
        Connection cn = DriverManager.getConnection(url, user, pass);
        // Statement st 操作数据库执行的工具
        Statement st = cn.createStatement();
        // 执行查询
        String sql="select * from emp";
        // 获得SQL查询语句的结果
        ResultSet rs = st.executeQuery(sql);
        // next判断是否还有下行数据,如果还有下一条数据,则返回true,否则false
        // 循环结果集
        while(rs.next()){
            // 通过字段名
            int id = rs.getInt("id");
            String worknumber = rs.getString("worknumber");
            String name = rs.getString("name");
            String sex = rs.getString("sex");
            int age = rs.getInt("age");
            String card = rs.getString("card");
            Date hiredate = rs.getDate("hiredate");
            int dno = rs.getInt("dno");
            int sal = rs.getInt("sal");
           
 System.out.println("id:"+id+",worknumber:"+worknumber+",name:"+name+",sex:"+sex
+",age:"+age+",card:"+card+",hiredate:"+hiredate+",dno:"+dno+",sal:"+sal);
       }
        // 释放资源
        rs.close();
        st.close();
        cn.close();
   }
}
使用下标获取表中列的值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34package com.iweb.test;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class Test4 {
    public static void main(String[] args)  throws  Exception {
        Class.forName("com.mysql.jdbc.Driver");
        InputStream is = 
Test4.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties prop = new Properties();
        prop.load(is);
        String url = prop.getProperty("url");
        String user = prop.getProperty("user");
        String pass = prop.getProperty("pass");
        Connection cn = DriverManager.getConnection(url, user, pass);
        // Statement st 操作数据库执行的工具
        Statement st = cn.createStatement();
        // 准备一条SQL语句
        String sql="select * from emp where id=20";
        // 执行SQL语句
        ResultSet rs = st.executeQuery(sql);
        if(rs.next()){
            // 通过下标
            int id = rs.getInt(1);
            String number = rs.getString(2);
            String name = rs.getString(3);
           
 System.out.println("id:"+id+"\t"+"number:"+number+"\t"+"name:"+name);
       }
        rs.close();
        st.close();
        cn.close();
 }
}
关于Result接口的注意事项
1)注意类型不要获取错了
2)使用完毕要关闭结果集ResultSet,再关闭Statement,再关闭Connection(关闭原则)
综合案例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61package com.iweb.test;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
public class UserLogin {
    public static void main(String[] args) {
        // 用户从控制台输入账号和密码去登录
        Scanner sc =new Scanner(System.in);
        System.out.println("请输入用户名");
        String username = sc.nextLine();
        System.out.println("请输入密码");
        String password = sc.nextLine();
        // 调用登录方法
        login(username,password);
   }
    /**
     * 登录功能
     * @param username 登录账号
     * @param password 登录密码
     */
    public static void login(String username,String password){
        Connection cn =null;
        Statement st =null;
        ResultSet rs=null;
        try {
            //1. 注册并加载驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.读取属性文件
            InputStream is = 
UserLogin.class.getClassLoader().getResourceAsStream("jdbc.properties");
            Properties prop = new Properties();
            prop.load(is);
            String url = prop.getProperty("url");
            String user = prop.getProperty("user");
            String pass = prop.getProperty("pass");
            cn = DriverManager.getConnection(url, user, pass);
            //3.连接Connection获得操作工具
            st = cn.createStatement();
            String sql="SELECT * FROM userinfo WHERE username='"+username+"' AND 
PASSWORD='"+password+"'";
            System.out.println(sql);
            rs = st.executeQuery(sql);
            if (rs.next()) {
                System.out.println("欢迎"+username+"登录本系统!!");
           }else{
                System.out.println("账号或密码错误,请重新登录!!");
           }
       } catch (Exception e) {
            e.printStackTrace();
       }finally {
            try {
                rs.close();
                st.close();
                cn.close();
           } catch (SQLException e) {
                e.printStackTrace();
           }
       }
   }
}
SQL注入问题(有面试题的喔)
当我们输出密码,发现账号和密码不正确竟然登录成功。
例如:执行以下结果机器现象
请输入用户名
admin
请输入密码
a’ or ‘1’=’1
SELECT * FROM userinfo WHERE username=’admin’ AND PASSWORD=’a’ or ‘1’=’1’
欢迎admin登录本系统!!
select * from userinfo where true 查询所有记录
以上案例出现的问题是:
我们让用户输入的密码和SQL语句进行字符串拼接,用户输入的内容作为SQL语句语法的一部分,改变
了原有SQL真正的意义,以上问题称为SQL注入,要解决SQL注入就不能让用户输入的密码和我们SQL
语句进行简单的字符串拼接。
使用Statement操作数据库表存在的弊端:
- 问题一:存在拼串操作,繁琐
 - 问题二:存在SQL注入问题
 
对于java而言,要防范SQL注入,只要用 PreparedStatement(从Statement扩展而来)取代
Statement就可以了。
PreparedStatement
PreparedStatement也叫做预处理对象,它是Statement的子接口,两者功能相似,但是如果多次执行SQL语句时PreparedStatement效率会更高,它还可以给SQL语句传递参数,避免SQL注入问题。
预编译:将SQL语句发送给数据库预编译,PreparedStatement会引用这预编译后的结果,可以多次传
入不同的参数给PreparedStatement对象并执行,减少SQL编译次数,提高效率。
使用PreparedStatement的步骤
1). 编写SQL语句时,未知参数用?占位1
String sql="SELECT * FROM userinfo WHERE username=? AND PASSWORD=?";
2).通过链接对象获得PreparedStatement对象
3).设置实际参数的值 setXXX(占位符的位置,赋予真实的值)
4).执行带参数的SQL语句
5).关闭资源
在使用数据库语言时请在SQL中先行写好之后移植过来
案例(登录操作)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60public class UserLogin {
    public static void main(String[] args) {
        // 用户从控制台输入账号和密码去登录
        Scanner sc =new Scanner(System.in);
        System.out.println("请输入用户名");
        String username = sc.nextLine();
        System.out.println("请输入密码");
        String password = sc.nextLine();
        // 调用登录方法
        login(username,password);
   }
    /**
     * 登录功能
     * @param username 登录账号
     * @param password 登录密码
     */
    public static void login(String username,String password){
        Connection cn =null;
        ResultSet rs=null;
        PreparedStatement ps=null;
        try {
            //1. 注册并加载驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.读取属性文件
            InputStream is = 
UserLogin.class.getClassLoader().getResourceAsStream("jdbc.properties");
            Properties prop = new Properties();
            prop.load(is);
            String url = prop.getProperty("url");
            String user = prop.getProperty("user");
            String pass = prop.getProperty("pass");
            cn = DriverManager.getConnection(url, user, pass);
            // 编写SQL语句时,未知参数用?占位
            String sql="SELECT * FROM userinfo WHERE username=? AND PASSWORD=?";
            // 通过连接获得预处理对象PreparedStatement
            ps = cn.prepareStatement(sql);
            // 设置实际参数的值 setXXX(占位符的位置,赋予真实的值)
            ps.setString(1,username);
            ps.setString(2,password);
            // 执行带参数的SQL语句
            rs = ps.executeQuery();
            if(rs.next()){
                System.out.println("登录成功:"+username);
           }else{
                System.out.println("账号或密码错误");
           }
       } catch (Exception e) {
            e.printStackTrace();
       }finally {
            try {
                rs.close();
                ps.close();
                cn.close();
           } catch (SQLException e) {
                e.printStackTrace();
           }
       }
   }
}
实际案例(模糊查询)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46public class EmpLikeTest {
    public static void main(String[] args) {
        Scanner sc =new Scanner(System.in);
        System.out.println("请输入要查询的职员姓名");
        String name = sc.next();
        Connection cn =null;
        PreparedStatement ps=null;
        ResultSet rs=null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            InputStream is = Test4.class.getClassLoader().
                    getResourceAsStream("jdbc.properties");
            Properties prop = new Properties();
            prop.load(is);
            String url = prop.getProperty("url");
            String user = prop.getProperty("user");
            String pass = prop.getProperty("pass");
            cn = DriverManager.getConnection(url, user, pass);
            // 未知参数?占位
            String sql="select * from emp where name like ?";
            // 获得预编译对象
            ps = cn.prepareStatement(sql);
            // 设置参数
            ps.setString(1,"%"+name+"%");
            // 返回结果集
            rs=ps.executeQuery();
            // 循环结果集
            while(rs.next()){
                System.out.println(rs.getString("name")+"\t"+
                        rs.getInt("age")+"\t"+
                        rs.getDate("hiredate"));
           }
       } catch (Exception e) {
            e.printStackTrace();
       } finally {
            try {
                rs.close();
                ps.close();
                cn.close();
           } catch (SQLException e) {
                e.printStackTrace();
           }
       }
   }
}
注意
使用jdbc进行模糊查询的时候”% _”不能写在SQL里面,写在执行的SQL有时候语法错误,所有我们要写setXXX()里面。
表与类的关系整张表看作是一个类,表的一行数据是类的一个实例对象。
1).查询一条数据,封装成一个Emp对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52public class TestById {
    public static void main(String[] args) {
        Connection cn=null;
        PreparedStatement ps=null;
        ResultSet rs=null;
        Emp emp=null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            InputStream is = 
Test3.class.getClassLoader().getResourceAsStream("jdbc.properties");
            Properties prop = new Properties();
            prop.load(is);
            String url = prop.getProperty("url");
            String user = prop.getProperty("user");
            String pass = prop.getProperty("pass");
            cn = DriverManager.getConnection(url, user, pass);
            String sql="select * from emp where id=?";
            // 预编译对象
            ps=cn.prepareStatement(sql);
            // 赋值操作
            ps.setInt(1,20);
            // 执行
            rs = ps.executeQuery();
            if(rs.next()){
                // 将查询的一行数据封装为Emp对象
                 emp=new Emp(rs.getInt("id"),
                        rs.getString("worknumber"),
                        rs.getString("name"),
                        rs.getString("sex"),
                        rs.getInt("age"),
                        rs.getString("card"),
                        rs.getDate("hiredate"),
                        rs.getInt("sal"),
                        rs.getInt("dno")
               );
           }
       } catch (Exception e) {
            e.printStackTrace();
       } finally {
            try {
                rs.close();
                ps.close();
                cn.close();
           } catch (SQLException e) {
                e.printStackTrace();
           }
       }
        System.out.println(emp.toString());
   }
}
2).将多条数据封装称集合 List1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52public class TestSelectAll {
    public static void main(String[] args) {
        Connection cn=null;
        PreparedStatement ps=null;
        ResultSet rs=null;
        List<Emp> list =new ArrayList<Emp>();
        try {
            Class.forName("com.mysql.jdbc.Driver");
            InputStream is = 
Test3.class.getClassLoader().getResourceAsStream("jdbc.properties");
            Properties prop = new Properties();
            prop.load(is);
            String url = prop.getProperty("url");
            String user = prop.getProperty("user");
            String pass = prop.getProperty("pass");
            cn = DriverManager.getConnection(url, user, pass);
            // 预处理SQL语句
            ps=cn.prepareStatement("select * from emp");
            // 没有?要赋值 
            // 执行返回结果集
            rs = ps.executeQuery();
            while(rs.next()){
                // 每一行数据封装成Emp对象
               Emp emp=new Emp(rs.getInt("id"),
                        rs.getString("worknumber"),
                        rs.getString("name"),
                        rs.getString("sex"),
                        rs.getInt("age"),
                        rs.getString("card"),
                        rs.getDate("hiredate"),
                        rs.getInt("sal"),
                        rs.getInt("dno")
               );
                // 将封装的Emp对象添加到集合中
                list.add(emp);
           }
       }catch (Exception e){
            e.printStackTrace();
       } finally {
            try {
                rs.close();
                ps.close();
                cn.close();
           } catch (SQLException e) {
                e.printStackTrace();
           }
       }
        for(Emp e : list){
            System.out.println(e.toString());
       }
   }
}
增删改操作
使用prepareStatement进行新增操作
1  | package com.iweb.test;  | 
使用prepareStatement进行修改操作
1  | public class TestEmpUpdate {  | 
使用prepareStatement进行删除操作
1  | public class TestEmpDelete {  | 
数据库工具类
需求:上面写的代码中出现了很多重复代码,可以把这些共同的代码抽取出来。
开始打包操作……呜噜噜
创建类JdbcUtils(其实这个很重要的,到时候我会发蓝图,所以只是简单总结一下)
JdbcUtils类的作用是封装数据库连接,以及释放数据库资源。
- 驱动程序、用户名、密码、URL定义为静态常量。
 - 注册驱动使用static静态代码块实现,只需要加载一次。
 - 提供获取数据库连接对象的静态方法。
 - 释放系统资料的方法
 
包含三个方法:
1)用户名、密码、URL、驱动类定义为静态常量
2)获得数据库连接 getConnecation()
3)关闭所有打开的资源1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82package com.iweb.utils;
import com.iweb.test.Test4;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
    // 1)用户名、密码、URL、驱动类定义为静态常量
    private static String url;
    private static String user;
    private static String password;
    private static String driver;
    // 文件的读取,只需要读取一次即可拿到这些值,静态代码块
    static{
        try {
            InputStream is = JdbcUtils.class.getClassLoader().
                    getResourceAsStream("jdbc.properties");
            Properties prop = new Properties();
            prop.load(is);
            url = prop.getProperty("url");
            user = prop.getProperty("user");
            password = prop.getProperty("pass");
            driver=prop.getProperty("driverClass");
            // 加载驱动
            Class.forName(driver);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    //  2)获得数据库连接 getConnecation()
    public static Connection getConnection() throws  Exception{
            return DriverManager.getConnection(url,user,password);
    }
    //  3)关闭所有打开的资源  关闭顺序 ResultSet-->Statement-->Connection
    public static void close(ResultSet rs, Statement st, Connection cn){
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(st!=null){
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(cn!=null){
            try {
                cn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    public static void close(Statement st, Connection cn){
        if(st!=null){
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(cn!=null){
            try {
                cn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
测试:
1  | public class TestJdbcUtils {  | 
数据库事务
事物是一组操作的集合,事务会把所有的操作作为一个整体一起向系统提交或撤销请求,这些操作要么同时成功,要么同时失败。
JDBC事务处理
1.数据一旦提交,就不可回滚。
2.数据什么时候意味着提交?
- 当一个连接对象被创建时,默认情况下是自动提交事务,每次执行一个SQL语句时,如果执行成功就会向数据自动提交,而不能回滚。
 - 关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的都是自己单独的连接,则无法保证。事务,即同一个事务的多个操作必须在同一个连接下。
 
JDBC程序中为了让多个SQL语句作为一个事务执行:
- 调用Connecation对象的setAutoCommit(false):以取消自动提交事务。
 - 在所有的SQL语句都成功执行后,调用commit()提交事务。
 - 在出现异常时,调用rollback()方法回滚事务
 
【案例:张三给李四转账】
1  | package com.iweb.test;  | 
这个部分灵活度很高,建议自己写代码,多调试几遍。话是什么说,但是还是多练吧
DAO结构
DAO:Data Access Object,数据访问对象。
项目文件标准文件结构(tree)1
2
3
4
5
6├───dao
│   ├───impl
│   └───interface
├───domain
├───service
└───utils
以上的各个文件夹的作用是:
- dao:数据访问层,负责与数据库进行交互,执行SQL语句。
 - domain:领域模型层,负责定义业务模型。
 - service:业务逻辑层,负责处理业务逻辑。
 - utils:工具类层,负责提供一些常用的工具方法。
 - impl:实现类层,负责实现DAO接口中的方法。
 - interface:接口层,定义DAO接口。
 - test:测试类层,负责编写测试代码。
 
反射
反射是指在运行时动态获取类的信息并调用其方法的能力。
反射机制
java反射机制在程序的运行时动态加载类并获取类的详情信息,从而操作类或对象的属性和方法。其本质是JVM得到Class对象之后,从而获取对象的各种信息。
反射机制主要包括以下几个方面:
- 类加载机制:将类的字节码文件加载到内存中,形成类的Class对象。
 - 反射API:提供了一组用于操作类、对象和方法的API。
 - 动态代理:在运行时创建代理对象,用于拦截和修改方法的调用。
 
类加载
类加载是指将类的字节码文件加载到内存中,形成类的Class对象的过程。
Java文件通过java编译器(javac)将java源代码(.java)编译而生成的。编译器将java代码转换成字节码,存储在class文件中,class文件需要加载到虚拟机中之后才能运行和使用。而虚拟机如何加载class文件,class文件中的信息进入到虚拟机后发生什么变化,这个过程涉及到了java的类加载机制。
类加载过程
主要由以下几个步骤组成:
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期可以概括为 7 个阶段:
加载、验证、准备、解析、初始化、使用和卸载。
1).加载(Loading)
加载是类加载过程的第一个阶段,在这个过程中,JVM会查找并加载类的二进制数据,通常通过类加载器(ClassLoader)完成。
类加载器:负责查找类文件,并将其加载到JVM中,JVM中内置了三个重要的ClassLoader:
- 启动类加载器(Bootstrap ClassLoader)
 - 扩展类加载器(Extension ClassLoader)
 - 系统类加载器(System ClassLoader)
 
2).链接(Linking)
链接阶段分为三个子阶段:验证、准备和解析
画个流程图
加载—>验证—>准备—>解析—>初始化—>使用—>卸载
- 验证(Verification):确保加载的类符合JVM规范,不会危害JVM的安全。
 - 准备(Preparation):为类的变量分配内存并设置默认初始化(int为0,引用对象为null)。
 - 解析(Resolution):将类、接口、字段和方法的符号引用转换为直接引用。
 
3).初始化(Initialization)
在初始化阶段,JVM会执行类中的静态初始化器和静态变量的赋值操作,初始化类变量和静态代码块。
4).使用(Using)
一旦类被初始化了,就可以被java程序使用了。可以通过new关键字创建类的实例。
5).卸载(Unloading)
当Java虚拟机结束时,或者在某个类不再被任务对象引用时,该类的类加载器可以卸载这个类。
1.2.2 双亲委派模型的核心机制
如果当一个类加载器收到类加载请求时,它不会先去加载,而是把这个请求委托父类加载器处理,如果父类加载器还存在其父类加载器,进一步向上委托,依次递归,最终将达到顶层的启动类加载器,如果父类加载器可以完成加载任务,就成功返回,倘如父类加载器无法完成加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
class
class类是java反射机制的基础,反射机制主要是通过class类来实现的。
在java编程语言中,Class类是一个特殊的类,它用于表示JVM运行的类或者接口的信息。Class对象是在加载类时由Java虚拟机自动构建的,也就是不需要我们创建,JVM已经帮我们创建好了。
每个类在java中都对应一个Class对象,这个对象保存了该类的结构信息,比如类名、属性、方法等等。class类是一个反射工具,能提供很多的方法用于获取类的各种信息(构造方法,属性,方法,注解)。
获取类对象
java.lang.Class类的实例表示正在运行的Java应用程序中的类和接口。
获取类对象的方式(三种)
- Class.forName(“类的全路径名”)
 - 类名.class
 - 对象.getClass()
1
2
3
4
5
6
7
8
9
10// 1. Class.forName("类的全路径名")
Class<?> c1 = Class.forName("com.bettfil.java快速学习速查.domain.Account");
System.out.println(c1);
// 2. 类名.class
Class<Account> c2 = Account.class;
System.out.println(c2);
// 3. 对象.getClass()
Account account = new Account();
Class<? extends Account> c3 = account.getClass();
System.out.println(c3); 
Class类的常用方法
- Field:获得对象的成员变量属性
 - Method:获得对象的方法
 - Constructor:获得对象的构造方法
- getConstructors:获得对象的构造方法
 
 - Annotation:获得对象的注解
- getAnnotations:获得对象的注解
 
 
获取属性值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Test2 {
    public static void main(String[] args) {
        // 获取类对象
        Class<Student> stuClass= Student.class;
        // 获取属性
        // getFields() 获得全部成员,包括继承而来的成员,不包括私有
        // getDeclaredFields 获得本来定义的成员,包括私有,但是不包括继承而来
        Field[] fields = stuClass.getDeclaredFields();
        for(Field field  : fields){
            // getModifiers() 修饰符都是通过数字编号获取,要想获取具体内容
            // filed.getType() 返回属性类型
            // field.getName()  获得属性名
            System.out.println(field.getModifiers()+"\t"+field.getType()+"\t"+field.getName());
        }
    }
}
如何看出来这个部分是反射呢?从哪个地方看出来的,有什么好处吗?
- 从类对象中获取属性,这个类对象是我们自己定义的,我们可以通过类对象获取到类中的属性。
 - 反射机制的灵活性,我们可以在运行时动态地获取类的信息,包括属性、方法、构造函数等。
 
获取方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Test3 {
    public static void main(String[] args) {
        try {
            Class stuClass = Class.forName("com.iweb.bean.Student");
            // getMethods() 获取类中定义的方法和继承而来的方法
            // getDeclaredMethods() 获取本类定义的方法,包括私有,但是不包括继承而来
            Method[] methods = stuClass.getDeclaredMethods();
            for(Method method : methods){
                System.out.println(method);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
获取构造函数
1  | public class Test4 {  | 
获得注解
1  | public class Test5 {  | 
1.5 反射应用
Constructor构造方法
1  | package com.iweb.test;  | 
- 指定参数的构造
 
1  | public class Test6 {  | 
Field
反射获取成员并使用
1  | public static void method3(){  | 
Method
调用本来的方法,一定要保证实例化对象,可以直接利用class类反射实例对象,反射调用方法。
1  | public static void method4(){  | 
调用有返回值有参数的方法
1  | public static void method5(){  | 
==总结==
在程序运行时,动态的加载类的信息(方法,属性,构造函数,注解),从而操作类或对象的属性和方法的这种机制称为Java反射。
实际案例
要求如下:
1.创建一个Emp类,定义私有的编码,私有的姓名和公开的年龄,3个属性,并创建set/get和toString方法,
 添加无参构造,1个参数构造,2个参数构造赋值。
 添加无参数无返回的study()方法
1  | public class Emp {  | 
2.测试以上三种获得类对象的方法
 (1)通过类拿到字节码对象
 new Emp().getClass()
 (2)通过类的class属性拿到字节码对象
 Emp.Class
 (3) 通过类的路径拿到字节码对象
 Class.forName()
3.获得类属性的方法。
 (1)获得所有共有属性
 (2)获得本类之中的属性
 (3)为私有属性赋值,以及获取值。
4.利用反射创建对象
1  | import java.lang.reflect.Constructor;  | 
5.利用反射执行study方法
1  | import java.lang.reflect.Method;  | 
注解
Java注解(Annotation)是JDK引入的一种注释机制,注解可以作用在类,属性,方法上等。注解可以做到不改变改变逻辑的前提下在代码中嵌入补充信息。
注解本质就是一个接口。
元注解
用来标注注解的注解叫做元注解(JDK的内置注解):
@Target:设置注解作用的位置:
- type
 - field
 - method
 
@Retention:设置注解作用的时机:
- RetentionPolicy.RUNTIME:运行时(大多数情况使用它)
 - RetentionPolicy.CLASS:注解在字节码文件中存在
 - RetentionPolicy.SOURCE:注解仅存在源码中
 
Junit
测试分类
用Java写完程序之后需要测试准确性,然后每一次测试如果出现错误,就需要重新修改重新写,这是一个比较麻烦的过程。
黑盒测试:不需要写代码,输入值,看程序是否能够输出期望的值。
白盒测试:需要写代码,关注程序的具体执行流程。Junit就是一个百盒测试的工具。
Junit概述
Junit是java编程语言的单元测试工具,是一个非常重要的测试工具(白盒测试)
特点
- Junit是一个开放源代码的测试工具
 - 提供注解来识别测试方法
 - Junit测试可以让你编写代码更快,并提供之质量
 - Junit在一个进度条中显示进度,如果运行良好则是绿色,如果运行失败,则是红色
 
使用步骤
步骤
1.将Junit的jar包导入到工程中
2.编写测试方法必须是共同的无参数无返回的测试方法。
3.在测试方面上使用@Test注解来标识该方法是一个测试方法。
4.选中测试方法右键通过Junit运行该方法
1  | public class Test7 {  | 
相关注解[应用]
| 注解 | 含义 | 
|---|---|
| @Test | 测试方法 | 
| @Before | 在测试方法之前运行 | 
| @After | 在测试方法之后运行 | 
代码示例
1  | package com.iweb.test;  | 
JDK8新特性
当前的java市场对JDK8和JDK9是主流,至于更高的11,17等,在企业中使用的最多的是JDK8和JDK9。

